package lang4j.generator;
import lang4j.parser.generated.*;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class Lang4jToDot {
    PrintWriter out;
    private String currentProduction;
    private final Transformer transformer;

    /** Creates a new Renderer that works on the given writer. <strong>Note</strong> that the
        Writer is not closed by the Renderer. You have to make sure that a) the writer is open
        as long as you intend to use this instance and b) that you close the writer when done.

        @param writer output writer*/
    public Lang4jToDot(Writer writer,Transformer transformer){
        this.transformer = transformer;
        out=new PrintWriter(writer);
    }
    public void renderProductionList(java.util.List productionList){
        for(Iterator iter=productionList.iterator();iter.hasNext();){
            Production production = (Production) iter.next();
            renderProduction(production);

        }
    }

   public static String grammarToString(Lang4jGrammar grammar,Transformer transformer){
        StringWriter writer=new StringWriter();
        Lang4jToDot renderer=new Lang4jToDot(writer,transformer);
        renderer.renderGrammar(grammar);
        return writer.getBuffer().toString();
    }
    public void renderGrammar(Lang4jGrammar grammar){
        out.print("digraph "+grammar.getName()+"{\n");
        out.print(" node [shape=record]");

        renderProductionList(grammar.getProductions());
        out.print("}");
    }

    public void renderProduction(Production production){
    production.accept(new ProductionVisitor(){
        public void visitEnumProduction(EnumProduction enumProduction){
            renderEnumProduction(enumProduction);
        }
        public void visitTypeProduction(TypeProduction typeProduction){
            renderTypeProduction(typeProduction);
        }
        public void visitListProduction(ListProduction listProduction){
            //renderListProduction(listProduction);
        }
    });
    }

    public void renderTypeProduction(TypeProduction typeProduction){

        final String name = typeProduction.getName();
        String cname=StringUtils.capitalize(name);
        this.currentProduction=name;

        out.print(name);
        String additionalStyle;
        if (typeProduction instanceof InterfaceProduction){
        additionalStyle = "style=dotted,";
        }
        else
        {
            additionalStyle="";
        }


        out.print(" ["+additionalStyle+"label=\"{<main>"+cname +"|" );
        Collection attributes=transformer.getAttributes(typeProduction);
        Collection supertypes=transformer.getDirectSuperTypes(typeProduction);
        for (Iterator iterator = supertypes.iterator(); iterator.hasNext();) {
            Production production = (Production) iterator.next();
            attributes.removeAll(transformer.getAttributes((TypeProduction) production));
        }
        for (Iterator iterator = attributes.iterator(); iterator.hasNext();) {
            JavaAttribute attribute = (JavaAttribute) iterator.next();
            out.print("<"+attribute.getName()+">"+attribute.getName());
            if (iterator.hasNext()){
                out.print("|");
            }
        }
        out.println("}\"];\n");

        for (Iterator iter = attributes.iterator(); iter.hasNext();) {
            JavaAttribute javaAttribute = (JavaAttribute) iter.next();
            renderAttribute(javaAttribute.getAttribute());
        }
        if (typeProduction instanceof InterfaceProduction){
            final InterfaceProduction interfaceProduction = (InterfaceProduction) typeProduction;
            renderImplements(interfaceProduction.getSubTypes(),name);
        }

    }


    public void renderProductionReference(ProductionReference productionReference){
        out.print(productionReference.getProduction().getName());
        out.print(" ");
    }

    public void renderEnumProduction(EnumProduction enumProduction){

        out.print(enumProduction.getName()+" [\n");
        out.print(" label=\"{");
        out.print(StringUtils.capitalize(enumProduction.getName())+"|");
        renderLabelledTerminalList(enumProduction.getValues());
        out.println("}\"];\n");

    }

    public void renderLabelledTerminal(LabelledTerminal labelledTerminal){
        renderTerminal(labelledTerminal.getTerminal());
        out.print(" ");
        out.print(":");
        out.print(labelledTerminal.getLabel());
        out.print(" ");
    }

    public void renderLabelledTerminalList(List labelledTerminalList){
        for(Iterator iter=labelledTerminalList.iterator();iter.hasNext();){
            LabelledTerminal labelledTerminal = (LabelledTerminal) iter.next();
            renderLabelledTerminal(labelledTerminal);
            if (iter.hasNext())         out.print("|");
        }
    }



    public void renderImplements(List productionReferenceList, final String parentName){
        for(Iterator iter=productionReferenceList.iterator();iter.hasNext();){
            TypeProduction typeProduction = (TypeProduction) iter.next();
        out.print(parentName+"->"+typeProduction.getName()+":n [sametail="+parentName+",arrowtail=onormal,arrowhead=none];\n");

        //if (iter.hasNext())         out.print("|");
        }
    }


    public void renderFactorList(List factorList){
        for(Iterator iter=factorList.iterator();iter.hasNext();){
            ProductionFactor productionFactor = (ProductionFactor) iter.next();
            renderProductionFactor(productionFactor);
            out.print("\n");
        }
    }

    public void renderProductionFactor(ProductionFactor productionFactor){
    productionFactor.accept(new ProductionFactorVisitor(){
        public void visitTerminal(Terminal terminal){
            //renderTerminal(terminal);
        }
        public void visitAttribute(Attribute attribute){
            renderAttribute(attribute);
        }
        public void visitOptionalAttributeList(OptionalAttributeList optionalAttributeList){
            renderOptionalAttributeList(optionalAttributeList);
        }
    });
    }

    public void renderOptionalAttributeList(OptionalAttributeList optionalAttributeList){
        renderFactorList(optionalAttributeList.getFactors());

    }

    public void renderFlatAttribute(FlatAttribute flatAttribute){
        flatAttribute.accept(new FlatAttributeVisitor(){
        public void visitIntLiteralAttribute(IntLiteralAttribute intLiteralAttribute) {
            //nada
        }

        public void visitDoubleLiteralAttribute(DoubleLiteralAttribute doubleLiteralAttribute) {
            //To change body of implemented methods use File | Settings | File Templates.
        }

        public void visitCharLiteralAttribute(CharLiteralAttribute charLiteralAttribute){
            //renderCharLiteralAttribute(charLiteralAttribute);
        }

        public void visitBooleanLiteralAttribute(BooleanLiteralAttribute booleanLiteralAttribute) {
            //To change body of implemented methods use File | Settings | File Templates.
        }

        public void visitStringLiteralAttribute(StringLiteralAttribute stringLiteralAttribute){
            //renderStringLiteralAttribute(stringLiteralAttribute);
        }
        public void visitIdentifierAttribute(IdentifierAttribute identifierAttribute){
            //renderIdentifierAttribute(identifierAttribute);
        }
        public void visitReferenceAttribute(ReferenceAttribute referenceAttribute){
            renderReferenceAttribute(referenceAttribute);
        }

            public void visitMultiLineStringAttribute(MultiLineStringAttribute multiLineStringAttribute) {
                //To change body of implemented methods use File | Settings | File Templates.
            }
        });
    }

    public void renderDeepAttribute(DeepAttribute deepAttribute){
        if (deepAttribute.getProduction() instanceof ListProduction){
            ListProduction lp = (ListProduction) deepAttribute.getProduction();
            TypeProduction target = lp.getType().getProduction();
            String style;
            if(lp.getType() instanceof ProductionReference){

                style = "[minlen=1,headlabel=\"*\",arrowtail=odiamond,arrowhead=none]";
            } else
            {
                style = "[minlen=0,headlabel=\"*\",arrowhead=vee]";

            }

            out.print(this.currentProduction+":"+deepAttribute.getName()+"->"+target.getName()+style +";");

        } else{
            out.print(this.currentProduction+":"+deepAttribute.getName()+"->"+deepAttribute.getProduction().getName()+"[headlabel=\"1\",arrowtail=odiamond,arrowhead=none,minlen=1];");
        }
        /*out.print(" ");
       out.print(":");
       out.print(deepAttribute.getName());
       out.print(" ");*/
    }

    public void renderCharLiteralAttribute(CharLiteralAttribute charLiteralAttribute){
        out.print("CHAR_LITERAL ");
        out.print(":");
        out.print(charLiteralAttribute.getName());
        out.print(" ");
    }

    public void renderStringLiteralAttribute(StringLiteralAttribute stringLiteralAttribute){
        out.print("LITERAL ");
        out.print(":");
        out.print(stringLiteralAttribute.getName());
        out.print(" ");
    }

    public void renderIdentifierAttribute(IdentifierAttribute identifierAttribute){
        out.print("IDENTIFIER_REGEXP ");
        out.print(":");
        out.print(identifierAttribute.getName());
        out.print(" ");
    }

    public void renderReferenceAttribute(ReferenceAttribute referenceAttribute){

        out.print(this.currentProduction+":"+referenceAttribute.getName()+"->"+referenceAttribute.getProduction().getName()+"[minlen=0,headlabel=\"1\",arrowhead=vee];");
    }

    public void renderAttribute(Attribute attribute){
    attribute.accept(new AttributeVisitor(){
        public void visitDeepAttribute(DeepAttribute deepAttribute){
            renderDeepAttribute(deepAttribute);
        }
        public void visitFlatAttribute(FlatAttribute flatAttribute){
            renderFlatAttribute(flatAttribute);
        }
    });
    }

    public void renderCharToken(CharToken charToken){
        out.print("'"+StringEscapeUtils.escapeJava(Character.toString(charToken.getText()))+"'");
        out.print(" ");
    }

    public void renderKeyword(Keyword keyword){
        out.print("\\\""+StringEscapeUtils.escapeJava(keyword.getText())+"\\\"");
        out.print(" ");
    }

    public void renderTerminal(Terminal terminal){
    terminal.accept(new TerminalVisitor(){
        public void visitCharToken(CharToken charToken){
            renderCharToken(charToken);
        }
        public void visitKeyword(Keyword keyword){
            renderKeyword(keyword);
        }
    });
    }




}
